<?php
/**
 * <pre>
 * DiabloStudios.Board v1.0
 * 
 * Various functions used in DiabloStudios.Board
 * </pre>
 * 
 * @copyright	(c) 2010 DiabloStudios
 * @license		GNU Affero GPL v3 or newer
 * @package		PHP-Fusion
 * @subpackage	DiabloStudios.Board
 * @link		http://diablostudios.pl
 * @version     $Id: functions.php 1 2011-03-20 11:56:30Z Bartek $
 */

function output_page($contents)
{

	echo $contents;

}
/**
 * Rebuilds settings.php
 */
function rebuild_settings()
{
	global $dsb;

	if (!file_exists(DSB_DIR . "includes/settings.php"))
	{
		$mode = "x";
	}
	else
	{
		$mode = "w";
	}

	$query = dbquery("SELECT name,value FROM " . DB_DSB_SETTINGS . " ORDER BY name ASC");

	while ($setting = dbarray($query))
	{
		$dsb->settings[$setting['name']] = $setting['value'];
		$setting['value'] = addcslashes($setting['value'], '\\"$');
		$settings .= "\$settings['{$setting['name']}'] = \"{$setting['value']}\";\n";
	}

	$settings = "<" . "?php\n/*********************************\ \n  DO NOT EDIT THIS FILE, PLEASE USE\n  THE SETTINGS EDITOR\n\*********************************/\n\n$settings\n?" . ">";
	$file = @fopen(DSB_DIR . "includes/settings.php", $mode);
	@fwrite($file, $settings);
	@fclose($file);

	$GLOBALS['settings'] = &$dsb->settings;
}

/**
 * Build the forum link.
 *
 * @param int The forum id of the forum.
 * @param int (Optional) The page number of the forum.
 * @return string The url to the forum.
 */
function get_forum_link($fid, $page = 0)
{
	if ($page > 0)
	{
		$link = "viewforum.php?forum_id={$fid}&amp;rowstart={$page}";
		return htmlspecialchars_uni($link);
	}
	else
	{
		$link = "viewforum.php?forum_id={$fid}";
		return htmlspecialchars_uni($link);
	}
}
/**
 * Build the thread link.
 *
 * @param int The thread id of the thread.
 * @param int (Optional) The page number of the thread.
 * @param string (Optional) The action we're performing (ex, lastpost, newpost, etc)
 * @return string The url to the thread.
 */
function get_thread_link($tid, $page = 0, $action = '')
{
	if ($page > 1)
	{
		if ($action)
		{
			$link = "viewthread.php?thread_id={$tid}&rowstart={$page}&action=($action}";
		}
		else
		{
			$link = "viewthread.php?thread_id={$tid}&rowstart={$page}";
		}
		return htmlspecialchars_uni($link);
	}
	else
	{
		if ($action)
		{
			$link = "viewthread.php?thread_id={$tid}&action=$action";
		}
		else
		{
			$link = "viewthread.php?thread_id={$tid}";
		}
		return htmlspecialchars_uni($link);
	}
}

/**
 * Build the post link.
 *
 * @param int The post ID of the post
 * @param int The thread id of the post.
 */
function get_post_link($pid, $tid = 0)
{
	if ($tid > 0)
	{
		$link = str_replace("{tid}", $tid, THREAD_URL_POST);
		$link = str_replace("{pid}", $pid, $link);
		return htmlspecialchars_uni($link);
	}
	else
	{
		$link = str_replace("{pid}", $pid, POST_URL);
		return htmlspecialchars_uni($link);
	}
}

/**
 * Get the forum of a specific forum id.
 *
 * @param int The forum id of the forum.
 * @param int (Optional) If set to 1, will override the active forum status
 * @return array The database row of a forum.
 */
function get_forum($fid, $active_override = 0)
{
	global $cache;
	static $forum_cache;

	if (!isset($forum_cache) || is_array($forum_cache))
	{
		$forum_cache = $cache->read("forums");
	}

	if (!$forum_cache[$fid])
	{
		return false;
	}

	if ($active_override != 1)
	{
		$parents = explode(",", $forum_cache[$fid]['parentlist']);
		if (is_array($parents))
		{
			foreach ($parents as $parent)
			{
				if ($forum_cache[$parent]['active'] == 0)
				{
					return false;
				}
			}
		}
	}

	return $forum_cache[$fid];
}

/**
 * Get the thread of a thread id.
 *
 * @param int The thread id of the thread.
 * @param boolean Whether or not to recache the thread.
 * @return string The database row of the thread.
 */
function get_thread($tid, $recache = false)
{
	global $db;
	static $thread_cache;

	if (isset($thread_cache[$tid]) && !$recache)
	{
		return $thread_cache[$tid];
	}
	else
	{
		$query = $db->simple_select("threads", "*", "tid='" . intval($tid) . "'");
		$thread = $db->fetch_array($query);

		if ($thread)
		{
			$thread_cache[$tid] = $thread;
			return $thread;
		}
		else
		{
			$thread_cache[$tid] = false;
			return false;
		}
	}
}

/**
 * Get the post of a post id.
 *
 * @param int The post id of the post.
 * @return string The database row of the post.
 */
function get_post($pid)
{
	global $db;
	static $post_cache;

	if (isset($post_cache[$pid]))
	{
		return $post_cache[$pid];
	}
	else
	{
		$query = $db->simple_select("posts", "*", "pid='" . intval($pid) . "'");
		$post = $db->fetch_array($query);

		if ($post)
		{
			$post_cache[$pid] = $post;
			return $post;
		}
		else
		{
			$post_cache[$pid] = false;
			return false;
		}
	}
}

/**
 * Formats a username based on their display group
 *
 * @param string The username
 * @param int The usergroup for the user (if not specified, will be fetched)
 * @param int The display group for the user (if not specified, will be fetched)
 * @return string The formatted username
 */
function format_name($username, $usergroup, $displaygroup = "")
{
	global $groupscache, $cache;

	if (!is_array($groupscache))
	{
		$groupscache = $cache->read("usergroups");
	}

	if ($displaygroup != 0)
	{
		$usergroup = $displaygroup;
	}

	$ugroup = $groupscache[$usergroup];
	$format = $ugroup['namestyle'];
	$userin = substr_count($format, "{username}");

	if ($userin == 0)
	{
		$format = "{username}";
	}

	$format = stripslashes($format);

	return str_replace("{username}", $username, $format);
}
/**
 * Generate a listing of page - pagination
 *
 * @param int The number of items
 * @param int The number of items to be shown per page
 * @param int The current page number
 * @param string The URL to have page numbers tacked on to (If {page} is specified, the value will be replaced with the page #)
 * @return string The generated pagination
 */
function multipage($count, $perpage, $page, $url, $breadcrumb = false)
{
	global $theme, $templates, $lang, $dsb;

	if ($count <= $perpage)
	{
		return;
	}

	$url = str_replace("&amp;", "&", $url);
	$url = htmlspecialchars_uni($url);

	$pages = ceil($count / $perpage);

	if ($page > 1)
	{
		$prev = $page - 1;
		$page_url = fetch_page_url($url, $prev);
		eval("\$prevpage = \"" . $templates->get("multipage_prevpage") . "\";");
	}

	// Maximum number of "page bits" to show
	if (!$dsb->settings['maxmultipagelinks'])
	{
		$dsb->settings['maxmultipagelinks'] = 5;
	}

	$from = $page - floor($dsb->settings['maxmultipagelinks'] / 2);
	$to = $page + floor($dsb->settings['maxmultipagelinks'] / 2);

	if ($from <= 0)
	{
		$from = 1;
		$to = $from + $dsb->settings['maxmultipagelinks'] - 1;
	}

	if ($to > $pages)
	{
		$to = $pages;
		$from = $pages - $dsb->settings['maxmultipagelinks'] + 1;
		if ($from <= 0)
		{
			$from = 1;
		}
	}

	if ($to == 0)
	{
		$to = $pages;
	}

	if ($from > 1)
	{
		if ($from - 1 == 1)
		{
			$lang->multipage_link_start = '';
		}

		$page_url = fetch_page_url($url, 1);
		eval("\$start = \"" . $templates->get("multipage_start") . "\";");
	}

	for ($i = $from; $i <= $to; ++$i)
	{
		$page_url = fetch_page_url($url, $i);
		if ($page == $i)
		{
			if ($breadcrumb == true)
			{
				eval("\$mppage .= \"" . $templates->get("multipage_page_link_current") . "\";");
			}
			else
			{
				eval("\$mppage .= \"" . $templates->get("multipage_page_current") . "\";");
			}
		}
		else
		{
			eval("\$mppage .= \"" . $templates->get("multipage_page") . "\";");
		}
	}

	if ($to < $pages)
	{
		if ($to + 1 == $pages)
		{
			$lang->multipage_link_end = '';
		}

		$page_url = fetch_page_url($url, $pages);
		eval("\$end = \"" . $templates->get("multipage_end") . "\";");
	}

	if ($page < $pages)
	{
		$next = $page + 1;
		$page_url = fetch_page_url($url, $next);
		eval("\$nextpage = \"" . $templates->get("multipage_nextpage") . "\";");
	}
	$lang->multipage_pages = $lang->sprintf($lang->multipage_pages, $pages);

	if ($breadcrumb == true)
	{
		eval("\$multipage = \"" . $templates->get("multipage_breadcrumb") . "\";");
	}
	else
	{
		eval("\$multipage = \"" . $templates->get("multipage") . "\";");
	}

	return $multipage;
}
/**
 * Build the forum permissions for a specific forum, user or group
 *
 * @param int The forum ID to build permissions for (0 builds for all forums)
 * @param int The user to build the permissions for (0 will select the uid automatically)
 * @param int The group of the user to build permissions for (0 will fetch it)
 * @return array Forum permissions for the specific forum or forums
 */
function forum_permissions($fid = 0, $uid = 0, $gid = 0)
{
	global $db, $cache, $groupscache, $forum_cache, $fpermcache, $dsb, $usercache, $cached_forum_permissions_permissions, $cached_forum_permissions;

	if ($uid == 0)
	{
		$uid = $dsb->user['user_id'];
	}

	if (!$gid || $gid == 0) // If no group, we need to fetch it

	{
		if ($uid != 0 && $uid != $dsb->user['user_id'])
		{
			if (!$usercache[$uid])
			{
				$query = $db->simple_select("users", "*", "uid='$uid'");
				$usercache[$uid] = $db->fetch_array($query);
			}

			$gid = $usercache[$uid]['usergroup'] . "," . $usercache[$uid]['additionalgroups'];
			$groupperms = usergroup_permissions($gid);
		}
		else
		{
			$gid = $dsb->user['usergroup'];

			if (isset($dsb->user['additionalgroups']))
			{
				$gid .= "," . $dsb->user['additionalgroups'];
			}

			$groupperms = $dsb->usergroup;
		}
	}

	if (!is_array($forum_cache))
	{
		$forum_cache = cache_forums();

		if (!$forum_cache)
		{
			return false;
		}
	}

	if (!is_array($fpermcache))
	{
		$fpermcache = $cache->read("forumpermissions");
	}

	if ($fid) // Fetch the permissions for a single forum

	{
		if (!$cached_forum_permissions_permissions[$gid][$fid])
		{
			$cached_forum_permissions_permissions[$gid][$fid] = fetch_forum_permissions($fid, $gid, $groupperms);
		}
		return $cached_forum_permissions_permissions[$gid][$fid];
	}
	else
	{
		if (!$cached_forum_permissions[$gid])
		{
			foreach ($forum_cache as $forum)
			{
				$cached_forum_permissions[$gid][$forum['fid']] = fetch_forum_permissions($forum['fid'], $gid, $groupperms);
			}
		}
		return $cached_forum_permissions[$gid];
	}
}

/**
 * Fetch the usergroup permissions for a specic group or series of groups combined
 *
 * @param mixed A list of groups (Can be a single integer, or a list of groups separated by a comma)
 * @return array Array of permissions generated for the groups
 */

function usergroup_permissions($gid = 0)
{
	global $cache, $groupscache, $grouppermignore, $groupzerogreater;

	if (!is_array($groupscache))
	{
		$groupscache = $cache->read("usergroups");
	}

	$groups = explode(",", $gid);

	if (count($groups) == 1)
	{
		return $groupscache[$gid];
	}

	foreach ($groups as $gid)
	{
		if (trim($gid) == "" || !$groupscache[$gid])
		{
			continue;
		}

		foreach ($groupscache[$gid] as $perm => $access)
		{
			if (!in_array($perm, $grouppermignore))
			{
				if (isset($usergroup[$perm]))
				{
					$permbit = $usergroup[$perm];
				}
				else
				{
					$permbit = "";
				}

				// 0 represents unlimited for numerical group permissions (i.e. private message limit) so take that into account.
				if (in_array($perm, $groupzerogreater) && ($access == 0 || $permbit === 0))
				{
					$usergroup[$perm] = 0;
					continue;
				}

				if ($access > $permbit || ($access == "yes" && $permbit == "no") || !$permbit) // Keep yes/no for compatibility?

				{
					$usergroup[$perm] = $access;
				}
			}
		}
	}

	return $usergroup;
}

/**
 * Fetches the permissions for a specific forum/group applying the inheritance scheme.
 * Called by forum_permissions()
 *
 * @param int The forum ID
 * @param string A comma separated list of usergroups
 * @param array Group permissions
 * @return array Permissions for this forum
 */
function fetch_forum_permissions($fid, $gid, $groupperms)
{
	global $groupscache, $forum_cache, $fpermcache, $dsb, $fpermfields;

	$groups = explode(",", $gid);

	if (!$fpermcache[$fid]) // This forum has no custom or inherited permissions so lets just return the group permissions

	{
		return $groupperms;
	}

	$current_permissions = array();

	foreach ($groups as $gid)
	{
		if ($groupscache[$gid])
		{
			$level_permissions = $fpermcache[$fid][$gid];

			// If our permissions arn't inherited we need to figure them out
			if (empty($level_permissions))
			{
				$parents = explode(',', $forum_cache[$fid]['parentlist']);
				rsort($parents);
				if (!empty($parents))
				{
					foreach ($parents as $parent_id)
					{
						if (!empty($fpermcache[$parent_id][$gid]))
						{
							$level_permissions = $fpermcache[$parent_id][$gid];
							break;
						}
					}

					// If we STILL don't have forum permissions we use the usergroup itself
					if (empty($level_permissions))
					{
						$level_permissions = $groupscache[$gid];
					}
				}
			}

			foreach ($level_permissions as $permission => $access)
			{
				if ($access >= $current_permissions[$permission] || ($access == "yes" && $current_permissions[$permission] == "no") || !$current_permissions[$permission])
				{
					$current_permissions[$permission] = $access;
				}
			}
		}
	}

	if (count($current_permissions) == 0)
	{
		$current_permissions = $groupperms;
	}
	return $current_permissions;
}
/**
 * Counts the number of subforums in a array([pid][disporder][fid]) starting from the pid
 *
 * @param array The array of forums
 * @return integer The number of sub forums
 */
function subforums_count($array)
{
	$count = 0;
	foreach ($array as $array2)
	{
		$count += count($array2);
	}

	return $count;
}

/**
 * Return a parent list for the specified forum.
 *
 * @param int The forum id to get the parent list for.
 * @return string The comma-separated parent list.
 */
function get_parent_list($fid)
{
	global $forum_cache;
	static $forumarraycache;

	if ($forumarraycache[$fid])
	{
		return $forumarraycache[$fid]['parentlist'];
	} elseif ($forum_cache[$fid])
	{
		return $forum_cache[$fid]['parentlist'];
	}
	else
	{
		cache_forums();
		return $forum_cache[$fid]['parentlist'];
	}
}

/**
 * Build a parent list of a specific forum, suitable for querying
 *
 * @param int The forum ID
 * @param string The column name to add to the query
 * @param string The joiner for each forum for querying (OR | AND | etc)
 * @param string The parent list of the forum - if you have it
 * @return string The query string generated
 */
function build_parent_list($fid, $column = "fid", $joiner = "OR", $parentlist = "")
{
	if (!$parentlist)
	{
		$parentlist = get_parent_list($fid);
	}

	$parentsexploded = explode(",", $parentlist);
	$builtlist = "(";
	$sep = '';

	foreach ($parentsexploded as $key => $val)
	{
		$builtlist .= "$sep$column='$val'";
		$sep = " $joiner ";
	}

	$builtlist .= ")";

	return $builtlist;
}

/**
 * Load the forum cache in to memory
 *
 * @param boolean True to force a reload of the cache
 */
function cache_forums($force = false)
{
	global $forum_cache, $cache;

	if ($force == true)
	{
		$forum_cache = $cache->read("forums", 1);
		return $forum_cache;
	}

	if (!$forum_cache)
	{
		$forum_cache = $cache->read("forums");
		if (!$forum_cache)
		{
			$cache->update_forums();
			$forum_cache = $cache->read("forums", 1);
		}
	}
	return $forum_cache;
}
/**
 * Checks for the length of a string, mb strings accounted for
 *
 * @param string The string to check the length of.
 * @return int The length of the string.
 */
function my_strlen($string)
{
	global $lang;

	$string = preg_replace("#&\#([0-9]+);#", "-", $string);

	if (strtolower($lang->settings['charset']) == "utf-8")
	{
		// Get rid of any excess RTL and LTR override for they are the workings of the devil
		$string = str_replace(dec_to_utf8(8238), "", $string);
		$string = str_replace(dec_to_utf8(8237), "", $string);

		// Remove dodgy whitespaces
		$string = str_replace(chr(0xCA), "", $string);
	}
	$string = trim($string);

	if (function_exists("mb_strlen"))
	{
		$string_length = mb_strlen($string);
	}
	else
	{
		$string_length = strlen($string);
	}

	return $string_length;
}

/**
 * Cuts a string at a specified point, mb strings accounted for
 *
 * @param string The string to cut.
 * @param int Where to cut
 * @param int (optional) How much to cut
 * @param bool (optional) Properly handle HTML entities?
 * @return int The cut part of the string.
 */
function my_substr($string, $start, $length = "", $handle_entities = false)
{
	if ($handle_entities)
	{
		$string = unhtmlentities($string);
	}
	if (function_exists("mb_substr"))
	{
		if ($length != "")
		{
			$cut_string = mb_substr($string, $start, $length);
		}
		else
		{
			$cut_string = mb_substr($string, $start);
		}
	}
	else
	{
		if ($length != "")
		{
			$cut_string = substr($string, $start, $length);
		}
		else
		{
			$cut_string = substr($string, $start);
		}
	}

	if ($handle_entities)
	{
		$cut_string = htmlspecialchars_uni($cut_string);
	}
	return $cut_string;
}

/**
 * lowers the case of a string, mb strings accounted for
 *
 * @param string The string to lower.
 * @return int The lowered string.
 */
function my_strtolower($string)
{
	if (function_exists("mb_strtolower"))
	{
		$string = mb_strtolower($string);
	}
	else
	{
		$string = strtolower($string);
	}

	return $string;
}

/**
 * Finds a needle in a haystack and returns it position, mb strings accounted for
 *
 * @param string String to look in (haystack)
 * @param string What to look for (needle)
 * @param int (optional) How much to offset
 * @return int false on needle not found, integer position if found
 */
function my_strpos($haystack, $needle, $offset = 0)
{
	if ($needle == '')
	{
		return false;
	}

	if (function_exists("mb_strpos"))
	{
		$position = mb_strpos($haystack, $needle, $offset);
	}
	else
	{
		$position = strpos($haystack, $needle, $offset);
	}

	return $position;
}

/**
 * ups the case of a string, mb strings accounted for
 *
 * @param string The string to up.
 * @return int The uped string.
 */
function my_strtoupper($string)
{
	if (function_exists("mb_strtoupper"))
	{
		$string = mb_strtoupper($string);
	}
	else
	{
		$string = strtoupper($string);
	}

	return $string;
}

/**
 * Returns any html entities to their original character
 *
 * @param string The string to un-htmlentitize.
 * @return int The un-htmlentitied' string.
 */
function unhtmlentities($string)
{
	// Replace numeric entities
	$string = preg_replace('~&#x([0-9a-f]+);~ei', 'unichr(hexdec("\\1"))', $string);
	$string = preg_replace('~&#([0-9]+);~e', 'unichr("\\1")', $string);

	// Replace literal entities
	$trans_tbl = get_html_translation_table(HTML_ENTITIES);
	$trans_tbl = array_flip($trans_tbl);

	return strtr($string, $trans_tbl);
}

/**
 * Returns any ascii to it's character (utf-8 safe).
 *
 * @param string The ascii to characterize.
 * @return int The characterized ascii.
 */
function unichr($c)
{
	if ($c <= 0x7F)
	{
		return chr($c);
	}
	else
		if ($c <= 0x7FF)
		{
			return chr(0xC0 | $c >> 6) . chr(0x80 | $c & 0x3F);
		}
		else
			if ($c <= 0xFFFF)
			{
				return chr(0xE0 | $c >> 12) . chr(0x80 | $c >> 6 & 0x3F) . chr(0x80 | $c & 0x3F);
			}
			else
				if ($c <= 0x10FFFF)
				{
					return chr(0xF0 | $c >> 18) . chr(0x80 | $c >> 12 & 0x3F) . chr(0x80 | $c >> 6 & 0x3F) . chr(0x80 | $c & 0x3F);
				}
				else
				{
					return false;
				}
}
/**
 * Show the inline error.
 *
 * @param string The error message.
 * @return string The formatted HTML error.
 */
function error($message)
{

	$error = "<div class='error_box'>";
	$error .= $message;
	$error .= "</div>";

	return $error;

}

/**
 * Builds a forum jump menu
 *
 * @param int The parent forum to start with
 * @param int The selected item ID
 * @param int If we need to add select boxes to this cal or not
 * @param int The current depth of forums we're at
 * @param int Whether or not to show extra items such as User CP, Forum home
 * @param boolean Ignore the showinjump setting and show all forums (for moderation pages)
 * @param array Array of permissions
 * @param string The name of the forum jump
 * @return string Forum jump items
 */
function build_forum_jump($pid = "0", $selitem = "", $addselect = "1", $depth = "", $showextras = "1", $showall = false, $permissions = "", $name = "fid")
{
	global $forum_cache, $jumpfcache, $permissioncache, $dsb, $selecteddone, $forumjump, $forumjumpbits, $gobutton, $theme, $templates, $locale;

	$pid = intval($pid);

	if ($permissions)
	{
		$permissions = $dsb->usergroup;
	}

	if (!is_array($jumpfcache))
	{
		if (!is_array($forum_cache))
		{
			cache_forums();
		}

		foreach ($forum_cache as $fid => $forum)
		{
			if ($forum['active'] != 0)
			{
				$jumpfcache[$forum['pid']][$forum['disporder']][$forum['fid']] = $forum;
			}
		}
	}

	if (!is_array($permissioncache))
	{
		$permissioncache = forum_permissions();
	}

	if (is_array($jumpfcache[$pid]))
	{
		foreach ($jumpfcache[$pid] as $main)
		{
			foreach ($main as $forum)
			{
				$perms = $permissioncache[$forum['fid']];

				if ($forum['fid'] != "0" && ($perms['canview'] != 0 || $dsb->settings['hideprivateforums'] == 0) && $forum['linkto'] == '' && ($forum['showinjump'] != 0 || $showall == true))
				{
					$optionselected = "";

					if ($selitem == $forum['fid'])
					{
						$optionselected = "selected=\"selected\"";
						$selecteddone = 1;
					}

					$forum['name'] = htmlspecialchars_uni(strip_tags($forum['name']));

					eval("\$forumjumpbits .= \"" . $templates->get("forumjump_bit") . "\";");

					if ($forum_cache[$forum['fid']])
					{
						$newdepth = $depth . "--";
						$forumjumpbits .= build_forum_jump($forum['fid'], $selitem, 0, $newdepth, $showextras, $showall);
					}
				}
			}
		}
	}

	if ($addselect)
	{
		if (!$selecteddone)
		{
			if (!$selitem)
			{
				$selitem = "default";
			}

			$jumpsel[$selitem] = 'selected="selected"';
		}

		if ($showextras == 0)
		{
			$template = "special";
		}
		else
		{
			$template = "advanced";
		}

		eval("\$forumjump = \"" . $templates->get("forumjump_" . $template) . "\";");
	}

	return $forumjump;
}

/**
 * Checks if a moderator has permissions to perform an action in a specific forum
 *
 * @param int The forum ID (0 assumes global)
 * @param string The action tyring to be performed. (blank assumes any action at all)
 * @param int The user ID (0 assumes current user)
 * @return bool Returns true if the user has permission, false if they do not
 */
function is_moderator($fid = "0", $action = "", $uid = "0")
{
	global $dsb, $cache;

	if ($uid == 0)
	{
		$uid = $dsb->user['uid'];
	}

	if ($uid == 0)
	{
		return false;
	}

	$user_perms = user_permissions($uid);
	if ($user_perms['issupermod'] == 1)
	{
		return true;
	}
	else
	{
		if (!$fid)
		{
			$modcache = $cache->read('moderators');
			if (!empty($modcache))
			{
				foreach ($modcache as $modusers)
				{
					if (isset($modusers['users'][$uid]) && $modusers['users'][$uid]['mid'])
					{
						return true;
					}
				}
			}
			return false;
		}
		else
		{
			$modperms = get_moderator_permissions($fid, $uid);

			if (!$action && $modperms)
			{
				return true;
			}
			else
			{
				if ($modperms[$action] == 1)
				{
					return true;
				}
				else
				{
					return false;
				}
			}
		}
	}
}

/**
 * Fetch the permissions for a specific user
 *
 * @param int The user ID
 * @return array Array of user permissions for the specified user
 */
function user_permissions($uid = 0)
{
	global $dsb, $cache, $groupscache, $user_cache;

	// If no user id is specified, assume it is the current user
	if ($uid == 0)
	{
		$uid = $dsb->user['uid'];
	}

	// User id does not match current user, fetch permissions
	if ($uid != $dsb->user['uid'])
	{
		// We've already cached permissions for this user, return them.
		if ($user_cache[$uid]['permissions'])
		{
			return $user_cache[$uid]['permissions'];
		}

		// This user was not already cached, fetch their user information.
		if (!$user_cache[$uid])
		{
			$user_cache[$uid] = get_user($uid);
		}

		// Collect group permissions.
		$gid = $user_cache[$uid]['usergroup'] . "," . $user_cache[$uid]['additionalgroups'];
		$groupperms = usergroup_permissions($gid);

		// Store group permissions in user cache.
		$user_cache[$uid]['permissions'] = $groupperms;
		return $groupperms;
	}
	// This user is the current user, return their permissions
	else
	{
		return $dsb->usergroup;
	}
}

/**
 * Get the user data of a user id.
 *
 * @param int The user id of the user.
 * @return array The users data
 */
function get_user($uid)
{
	global $dsb, $db;
	static $user_cache;

	$uid = intval($uid);

	if ($uid == $dsb->user['uid'])
	{
		return $dsb->user;
	} elseif (isset($user_cache[$uid]))
	{
		return $user_cache[$uid];
	}
	else
	{
		$query = dbquery("SELECT * FROM " . DB_USERS . " u
                            LEFT JOIN " . DB_DSB_USERS . " u2 ON u2.uid=u.user_id
                            WHERE u.user_id={$uid}");
		$user_cache[$uid] = dbarray($query);

		return $user_cache[$uid];
	}
}
/**
 * Custom function for formatting numbers.
 *
 * @param int The number to format.
 * @return int The formatted number.
 */
function my_number_format($number)
{
	global $dsb;

	if ($number == "-")
	{
		return $number;
	}

	if (is_int($number))
	{
		return number_format($number, 0, $dsb->settings['decpoint'], $dsb->settings['thousandssep']);
	}
	else
	{
		$parts = explode('.', $number);

		if (isset($parts[1]))
		{
			$decimals = my_strlen($parts[1]);
		}
		else
		{
			$decimals = 0;
		}

		return number_format((double)$number, $decimals, $dsb->settings['decpoint'], $dsb->settings['thousandssep']);
	}
}
/**
 * Turn a unix timestamp in to a "friendly" date/time format for the user.
 *
 * @param string A date format according to PHP's date structure.
 * @param int The unix timestamp the date should be generated for.
 * @param int The offset in hours that should be applied to times. (timezones)
 * @param int Whether or not to use today/yesterday formatting.
 * @param boolean Whether or not to use the adodb time class for < 1970 or > 2038 times
 * @return string The formatted timestamp.
 */
function my_date($format, $stamp = "", $offset = "", $ty = 1, $adodb = false)
{
	global $dsb, $locale, $dsbadmin;

	// If the stamp isn't set, use TIME_NOW
	if (empty($stamp))
	{
		$stamp = TIME_NOW;
	}

	if (!$offset && $offset != '0')
	{
		if ($dsb->user['uid'] != 0 && array_key_exists("timezone", $dsb->user))
		{
			$offset = $dsb->user['timezone'];
			$dstcorrection = $dsb->user['dst'];
		} elseif (defined("IN_ADMINCP"))
		{
			$offset = $dsbadmin['timezone'];
			$dstcorrection = $dsbadmin['dst'];
		}
		else
		{
			$offset = $dsb->settings['timezoneoffset'];
			$dstcorrection = $dsb->settings['dstcorrection'];
		}

		// If DST correction is enabled, add an additional hour to the timezone.
		if ($dstcorrection == 1)
		{
			++$offset;
			if (my_substr($offset, 0, 1) != "-")
			{
				$offset = "+" . $offset;
			}
		}
	}

	if ($offset == "-")
	{
		$offset = 0;
	}

	if ($adodb == true && function_exists('adodb_date'))
	{
		$date = adodb_date($format, $stamp + ($offset * 3600));
	}
	else
	{
		$date = gmdate($format, $stamp + ($offset * 3600));
	}

	if ($dsb->settings['dateformat'] == $format && $ty)
	{
		$stamp = TIME_NOW;

		if ($adodb == true && function_exists('adodb_date'))
		{
			$todaysdate = adodb_date($format, $stamp + ($offset * 3600));
			$yesterdaysdate = adodb_date($format, ($stamp - 86400) + ($offset * 3600));
		}
		else
		{
			$todaysdate = gmdate($format, $stamp + ($offset * 3600));
			$yesterdaysdate = gmdate($format, ($stamp - 86400) + ($offset * 3600));
		}

		if ($todaysdate == $date)
		{
			$date = $locale['today'];
		}
		else
			if ($yesterdaysdate == $date)
			{
				$date = $locale['yesterday'];
			}
	}

	return $date;
}

/**
 * Custom function for htmlspecialchars which takes in to account unicode
 *
 * @param string The string to format
 * @return string The string with htmlspecialchars applied
 */
function htmlspecialchars_uni($message)
{
	$message = preg_replace("#&(?!\#[0-9]+;)#si", "&amp;", $message); // Fix & but allow unicode
	$message = str_replace("<", "&lt;", $message);
	$message = str_replace(">", "&gt;", $message);
	$message = str_replace("\"", "&quot;", $message);
	return $message;
}

/**
 * Builds a CSV parent list for a particular forum.
 *
 * @param int The forum ID
 * @param string Optional separator - defaults to comma for CSV list
 * @return string The built parent list
 */
function make_parent_list($fid, $navsep = ",")
{
	global $pforumcache;

	if (!$pforumcache)
	{
		$query = dbquery("SELECT f2.forum_name, f.fid, f.pid FROM " . DB_DSB_FORUMS . " f 
                            LEFT JOIN " . DB_FORUMS . " f2 ON f.fid=f2.forum_id ORDER BY forum_order, pid");
		while ($forum = dbarray($query))
		{
			$pforumcache[$forum['fid']][$forum['pid']] = $forum;
		}
	}

	reset($pforumcache);
	reset($pforumcache[$fid]);

	foreach ($pforumcache[$fid] as $key => $forum)
	{
		if ($fid == $forum['fid'])
		{
			if ($pforumcache[$forum['pid']])
			{
				$navigation = make_parent_list($forum['pid'], $navsep) . $navigation;
			}

			if ($navigation)
			{
				$navigation .= $navsep;
			}
			$navigation .= $forum['fid'];
		}
	}
	return $navigation;
}

function save_quick_perms($fid)
{
	global $db, $inherit, $canview, $canpostthreads, $canpostreplies, $canpostpolls, $canpostattachments, $cache;

	$permission_fields = array();

	$field_list = show_fields_from(DB_DSB_FORUMPERMS);
	foreach ($field_list as $field)
	{

		if (strpos($field['Field'], 'can') !== false)
		{
			$permission_fields[$field['Field']] = 1;
		}
	}

	// "Can Only View Own Threads" permission is a forum permission only option
	$usergroup_permission_fields = $permission_fields;
	unset($usergroup_permission_fields['canonlyviewownthreads']);

	$query = dbquery("SELECT gid FROM " . DB_DSB_USERGROUPS);
	while ($usergroup = dbarray($query))
	{
		$query2 = dbquery("SELECT " . dsb_escape_string(implode(',', array_keys($permission_fields))) . " FROM " . DB_DSB_FORUMPERMS . " WHERE fid='{$fid}' AND gid='{$usergroup['gid']}' LIMIT 1");
		$existing_permissions = dbarray($query2);

		if (!$existing_permissions)
		{
			$query2 = dbquery("SELECT " . dsb_escape_string(implode(',', array_keys($usergroup_permission_fields))) . "  FROM " . DB_DSB_USERGROUPS . " WHERE gid='{$usergroup['gid']}' LIMIT 1");
			$existing_permissions = dbarray($query2);
		}

		// Delete existing permissions
		dbquery("DELETE FROM " . DB_DSB_FORUMPERMS . " WHERE fid='{$fid}' AND gid='{$usergroup['gid']}'");

		// Only insert the new ones if we're using custom permissions
		if ($inherit[$usergroup['gid']] != 1)
		{
			if ($canview[$usergroup['gid']] == 1)
			{
				$pview = 1;
			}
			else
			{
				$pview = 0;
			}

			if ($canpostthreads[$usergroup['gid']] == 1)
			{
				$pthreads = 1;
			}
			else
			{
				$pthreads = 0;
			}

			if ($canpostreplies[$usergroup['gid']] == 1)
			{
				$preplies = 1;
			}
			else
			{
				$preplies = 0;
			}

			if ($canpostpolls[$usergroup['gid']] == 1)
			{
				$ppolls = 1;
			}
			else
			{
				$ppolls = 0;
			}

			if ($canpostattachments[$usergroup['gid']] == 1)
			{
				$pattachments = 1;
			}
			else
			{
				$pattachments = 0;
			}

			if (!$preplies && !$pthreads)
			{
				$ppost = 0;
			}
			else
			{
				$ppost = 1;
			}

			$insertquery = array("fid" => intval($fid), "gid" => intval($usergroup['gid']), "canview" => intval($pview), "canpostthreads" => intval($pthreads), "canpostreplys" => intval($preplies), "canpostattachments" => intval($pattachments), "canpostpolls" => intval($ppolls), );

			foreach ($permission_fields as $field => $value)
			{
				if (array_key_exists($field, $insertquery))
				{
					continue;
				}

				$insertquery[dsb_escape_string($field)] = intval($existing_permissions[$field]);
			}

			dbinsert(DB_DSB_FORUMPERMS, $insertquery);
		}
	}
	$cache->update_forumpermissions();
}
/********************************/
//       Cookie Functions       //
/********************************/
/**
 * DS.Board setcookie() wrapper.
 *
 * @param string The cookie identifier.
 * @param string The cookie value.
 * @param int The timestamp of the expiry date.
 * @param boolean True if setting a HttpOnly cookie (supported by IE, Opera 9, Konqueror)
 */
function my_setcookie($name, $value = "", $expires = "", $httponly = false)
{
	global $dsb;

	if (!$dsb->settings['cookiepath'])
	{
		$dsb->settings['cookiepath'] = "/";
	}

	if ($expires == -1)
	{
		$expires = 0;
	} elseif ($expires == "" || $expires == null)
	{
		$expires = TIME_NOW + (60 * 60 * 24 * 365); // Make the cookie expire in a years time
	}
	else
	{
		$expires = TIME_NOW + intval($expires);
	}

	$dsb->settings['cookiepath'] = str_replace(array("\n", "\r"), "", $dsb->settings['cookiepath']);
	$dsb->settings['cookiedomain'] = str_replace(array("\n", "\r"), "", $dsb->settings['cookiedomain']);
	$dsb->settings['cookieprefix'] = str_replace(array("\n", "\r", " "), "", $dsb->settings['cookieprefix']);

	// Versions of PHP prior to 5.2 do not support HttpOnly cookies and IE is buggy when specifying a blank domain so set the cookie manually
	$cookie = "Set-Cookie: {$dsb->settings['cookieprefix']}{$name}=" . urlencode($value);

	if ($expires > 0)
	{
		$cookie .= "; expires=" . @gmdate('D, d-M-Y H:i:s \\G\\M\\T', $expires);
	}

	if (!empty($dsb->settings['cookiepath']))
	{
		$cookie .= "; path={$dsb->settings['cookiepath']}";
	}

	if (!empty($dsb->settings['cookiedomain']))
	{
		$cookie .= "; domain={$dsb->settings['cookiedomain']}";
	}

	if ($httponly == true)
	{
		$cookie .= "; HttpOnly";
	}

	$dsb->cookies[$name] = $value;

	header($cookie, false);
}

/**
 * Unset a cookie set by DS.Board.
 *
 * @param string The cookie identifier.
 */
function my_unsetcookie($name)
{
	global $dsb;

	$expires = -3600;
	my_setcookie($name, "", $expires);

	unset($dsb->cookies[$name]);
}
/**
 * Get the contents from a serialised cookie array.
 *
 * @param string The cookie identifier.
 * @param int The cookie content id.
 * @return array|boolean The cookie id's content array or false when non-existent.
 */
function my_get_array_cookie($name, $id)
{
	global $dsb;

	if (!isset($dsb->cookies[$name]))
	{
		return false;
	}

	$cookie = unserialize($dsb->cookies[$name]);

	if (isset($cookie[$id]))
	{
		return $cookie[$id];
	}
	else
	{
		return 0;
	}
}

/**
 * Set a serialised cookie array.
 *
 * @param string The cookie identifier.
 * @param int The cookie content id.
 * @param string The value to set the cookie to.
 */
function my_set_array_cookie($name, $id, $value)
{
	global $dsb;

	$cookie = $dsb->cookies;
	$newcookie = unserialize($cookie[$name]);
	$newcookie[$id] = $value;
	$newcookie = serialize($newcookie);
	my_setcookie("$name", addslashes($newcookie));

	// Make sure our current viarables are up-to-date as well
	$dsb->cookies['mybb'][$name] = $newcookie;
}
/********************************/
//         MySQL Functions      //
/********************************/
/**

 * Escape a string according to the MySQL escape format.
 *
 * @param string The string to be escaped.
 * @return string The escaped string.
 */
function dsb_escape_string($string)
{
	if (function_exists("mysql_real_escape_string"))
	{
		$string = mysql_real_escape_string($string);
	}
	else
	{
		$string = addslashes($string);
	}
	return $string;
}
/**
 * Build an insert query from an array.
 *
 * @param string The table name to perform the query on.
 * @param array An array of fields and their values.
 * @return int The insert ID if available
 */
function dbinsert($table, $array)
{
	if (!is_array($array))
	{
		return false;
	}
	$fields = "`" . implode("`,`", array_keys($array)) . "`";
	$values = implode("','", $array);
	dbquery("
			INSERT 
			INTO {$table} (" . $fields . ") 
			VALUES ('" . $values . "')
		");
	return insert_id();
}
/**
 * Return the last id number of inserted data.
 *
 * @return int The id number.
 */
function insert_id()
{
	global $link;
	return mysql_insert_id($link);
}
/**
 * Return the number of rows resulting from a query.
 *
 * @param resource The query ID.
 * @return int The number of rows in the result.
 */
function dbnumrows($query)
{
	return mysql_num_rows($query);
}
/**
 * Show the "show fields from" command for a specific table.
 *
 * @param string The name of the table.
 * @return string Field info for that table
 */
function show_fields_from($table)
{
	$query = dbquery("SHOW FIELDS FROM {$table}");
	while ($field = dbarray($query))
	{
		$field_info[] = $field;
	}
	return $field_info;
}
/**
 * Build an update query from an array.
 *
 * @param string The table name to perform the query on.
 * @param array An array of fields and their values.
 * @param string An optional where clause for the query.
 * @param string An optional limit clause for the query.
 * @param boolean An option to quote incoming values of the array.
 * @return resource The query data.
 */
function dbupdate($table, $array, $where = "", $limit = "", $no_quote = false)
{
	if (!is_array($array))
	{
		return false;
	}

	$comma = "";
	$query = "";
	$quote = "'";

	if ($no_quote == true)
	{
		$quote = "";
	}

	foreach ($array as $field => $value)
	{
		$query .= $comma . "`" . $field . "`={$quote}{$value}{$quote}";
		$comma = ', ';
	}

	if (!empty($where))
	{
		$query .= " WHERE $where";
	}

	if (!empty($limit))
	{
		$query .= " LIMIT $limit";
	}

	return dbquery("
			UPDATE {$table}
			SET $query
		");
}
/**
 * Return a specific field from a query.
 *
 * @param resource The query ID.
 * @param string The name of the field to return.
 * @param int The number of the row to fetch it from.
 */
function dbfetchfield($query, $field, $row = false)
{
	if ($row === false)
	{
		$array = dbarray($query);
		return $array[$field];
	}
	else
	{
		return mysql_result($query, $row, $field);
	}
}
/********************************/
//        Locale Functions      //
/********************************/
/**
 * Custom sprintf function
 * 
 * @param string The strings to format
 * @return string Formatted string
 */
function ds_sprintf($string)
{
	$arg_list = func_get_args();
	$num_args = count($arg_list);

	for ($i = 1; $i < $num_args; $i++)
	{
		$string = str_replace('{' . $i . '}', $arg_list[$i], $string);
	}

	return $string;
}

/********************************/
//     Indicator Functions      //
/********************************/
/**
 * Mark a particular thread as read for the current user.
 *
 * @param int The thread ID
 * @param int The forum ID of the thread
 */
function mark_thread_read($tid, $fid)
{
	global $dsb, $db;

	// Can only do "true" tracking for registered users
	if ($dsb->settings['threadreadcut'] > 0 && $dsb->user['uid'])
	{
		// For registered users, store the information in the database.
		switch ($db->type)
		{
			case "pgsql":
			case "sqlite":
				$db->replace_query("threadsread", array('tid' => $tid, 'uid' => $dsb->user['uid'], 'dateline' => TIME_NOW), array("tid", "uid"));
				break;
			default:
				$db->write_query("
					REPLACE INTO " . TABLE_PREFIX . "threadsread (tid, uid, dateline)
					VALUES('$tid', '{$dsb->user['uid']}', '" . TIME_NOW . "')
				");
		}
	}
	// Default back to cookie marking
	else
	{
		my_set_array_cookie("threadread", $tid, TIME_NOW);
	}

	$unread_count = fetch_unread_count($fid);
	if ($unread_count == 0)
	{
		mark_forum_read($fid);
	}
}

/**
 * Fetches the number of unread threads for the current user in a particular forum.
 *
 * @param string The forums (CSV list)
 * @return int The number of unread threads
 */
function fetch_unread_count($fid)
{
	global $db, $dsb;

	$cutoff = TIME_NOW - $dsb->settings['threadreadcut'] * 60 * 60 * 24;

	if ($dsb->user['uid'] == 0)
	{
		$comma = '';
		$tids = '';
		$threadsread = unserialize($dsb->cookies['mybb']['threadread']);
		$forumsread = unserialize($dsb->cookies['mybb']['forumread']);
		if (is_array($threadsread))
		{
			foreach ($threadsread as $key => $value)
			{
				$tids .= $comma . intval($key);
				$comma = ',';
			}
		}

		if (!empty($tids))
		{
			$count = 0;

			// We set a limit to 100 otherwise it'll become too processor intensive, especially if we have many threads.
			$query = $db->query("
				SELECT lastpost, tid, fid
				FROM " . TABLE_PREFIX . "threads
				WHERE visible=1 AND closed NOT LIKE 'moved|%' AND fid IN ($fid) AND tid IN ($tids) AND lastpost > '{$cutoff}'
				LIMIT 100
			");
			while ($thread = $db->fetch_array($query))
			{
				if ($thread['lastpost'] > intval($threadsread[$thread['tid']]) && $thread['lastpost'] > intval($forumsread[$thread['fid']]))
				{
					++$count;
				}
			}
			return $count;
		}
	}
	else
	{

		$query = dbquery("
					SELECT COUNT(t.tid) AS unread_count
					FROM " . DB_DSB_THREADS . " t
					LEFT JOIN " . DB_DSB_THREADSR . " tr ON (tr.tid=t.tid AND tr.uid='{$dsb->user['user_id']}')
					LEFT JOIN " . DB_DSB_FORUMSREAD . " fr ON (fr.fid=t.fid AND fr.uid='{$dsb->user['user_id']}')
					WHERE t.visible=1 AND t.closed NOT LIKE 'moved|%' AND t.fid IN ($fid) AND t.lastpost > IFNULL(tr.dateline,$cutoff) AND t.lastpost > IFNULL(fr.dateline,$cutoff) AND t.lastpost>$cutoff
				");

		return dbfetchfield($query, "unread_count");
	}
}

/**
 * Mark a particular forum as read.
 *
 * @param int The forum ID
 */
function mark_forum_read($fid)
{
	global $dsb, $db;

	// Can only do "true" tracking for registered users
	if ($dsb->settings['threadreadcut'] > 0 && $dsb->user['uid'])
	{
		switch ($db->type)
		{
			case "pgsql":
			case "sqlite":
				add_shutdown(array($db, "replace_query"), array("forumsread", array('fid' => $fid, 'uid' => $dsb->user['uid'], 'dateline' => TIME_NOW), array("fid", "uid")));
				break;
			default:
				$db->shutdown_query("
					REPLACE INTO " . TABLE_PREFIX . "forumsread (fid, uid, dateline)
					VALUES('{$fid}', '{$dsb->user['uid']}', '" . TIME_NOW . "')
				");
		}
	}
	// Mark in a cookie
	else
	{
		my_set_array_cookie("forumread", $fid, TIME_NOW);
	}
}

/**
 * Marks all forums as read.
 *
 */
function mark_all_forums_read()
{
	global $dsb, $db, $cache;

	// Can only do "true" tracking for registered users
	if ($dsb->user['uid'] > 0)
	{
		$db->update_query("users", array('lastvisit' => TIME_NOW), "uid='" . $dsb->user['uid'] . "'");
		require_once MYBB_ROOT . "inc/functions_user.php";
		update_pm_count('', 2);

		if ($dsb->settings['threadreadcut'] > 0)
		{
			// Need to loop through all forums and mark them as read
			$forums = $cache->read('forums');

			$update_count = ceil(count($forums) / 20);

			if ($update_count < 15)
			{
				$update_count = 15;
			}

			$mark_query = '';
			$done = 0;
			foreach (array_keys($forums) as $fid)
			{
				switch ($db->type)
				{
					case "pgsql":
					case "sqlite":
						$mark_query[] = array('fid' => $fid, 'uid' => $dsb->user['uid'], 'dateline' => TIME_NOW);
						break;
					default:
						if ($mark_query != '')
						{
							$mark_query .= ',';
						}
						$mark_query .= "('{$fid}', '{$dsb->user['uid']}', '" . TIME_NOW . "')";
				}
				++$done;

				// Only do this in loops of $update_count, save query time
				if ($done % $update_count)
				{
					switch ($db->type)
					{
						case "pgsql":
						case "sqlite":
							foreach ($mark_query as $replace_query)
							{
								add_shutdown(array($db, "replace_query"), array("forumsread", $replace_query, array("fid", "uid")));
							}
							$mark_query = array();
							break;
						default:
							$db->shutdown_query("
								REPLACE INTO " . TABLE_PREFIX . "forumsread (fid, uid, dateline)
								VALUES {$mark_query}
							");
							$mark_query = '';
					}
				}
			}

			if ($mark_query != '')
			{
				switch ($db->type)
				{
					case "pgsql":
					case "sqlite":
						foreach ($mark_query as $replace_query)
						{
							add_shutdown(array($db, "replace_query"), array("forumsread", $replace_query, array("fid", "uid")));
						}
						break;
					default:
						$db->shutdown_query("
							REPLACE INTO " . TABLE_PREFIX . "forumsread (fid, uid, dateline)
							VALUES {$mark_query}
						");
				}
			}
		}
	}
	else
	{
		my_setcookie("mybb[lastvisit]", TIME_NOW);
		my_unsetcookie("mybb[threadread]");
		my_unsetcookie("mybb[forumread]");
	}
}

?>